'''
huoni PLC通讯 for scan
点位名称和功能
序号	英文名	中文名	功能
1	LoadEmptyCarton	空盒上料	引导	无
2	UnloadFullCarton	满盒下料	无	无
3	PlaceCarton	纸盒放置	无	检测
4	PlaceBat	电池放置	引导	检测
5	PlacePhone	手机放置	引导	检测
6	PlaceCable	数据线放置	引导	检测
7	PlaceStrap	腕带放置	引导	检测
8	PlaceScrew	螺丝放置	引导	检测
9	PlaceManual	说明书放置	引导	检测
	PlaceLicence	许可证放置	引导	检测

'''
import random
import sys
import threading
import time
import traceback

import snap7
# sys.path.append(r'D:\code\huoNiWeiEr\project_huoni_v2\neith_fyluoding\plc_S7')
# from plc_S7.s7_client import S7Client
# # from .s7_client import S7Client
try:
    from .ly1_factory import Lay1PLCFactory
except:
    from ly1_factory import Lay1PLCFactory

from log import logger

class Config:

    # ip = '127.0.0.1' # 本地测试用 启动模拟服务端命令 snap7-server
    # port = 1102
    # rack = 0
    # slot = 0
    # dbnumber = 0

    ip = '192.168.0.1'
    port = 102
    rack = 0
    slot = 0
    dbnumber = 219

    # MainTh
    # is_ready: the software is ready or not.
    # is_change_product_chufa: trigger of changing product (set)
    # sku: product name
    MainTh = {'is_ready': [212,2], 'is_change_product_chufa': [212,3], 'sku':[212,112],'sku_int':[212,4],
              'is_upload_chufa':[212, 110], 'is_upload_done': [212, 111],
              # 'is_upload_chufa': [212, 1], 'is_upload_done': [212, 0],
              'is_print_done': [212, 1], 'is_print_chufa': [212, 0],
              'serial_number_to_print': [212, 58],  'serial_number_to_upload':[212,112], 'place_num': [148,72],
              # 'is_place_bat': [148, 66],'is_place_cable': [148,67],'is_place_chdq': [148,70],
              # 'is_place_instruction': [148, 69],'is_place_licence': [148,71],'is_place_strap': [148,68],
              } #

    # 点位2 电池放置
    PlaceBat = { # 扫描结果（get），比对结果（set）, 比对触发(get), 是否需要比对(set) 1 ok 2 false
                'scan_res': 4, 'check_res': 2, 'is_check_chufa': 1, 'need_check':[148,52],'is_place': [148, 66],'device_name': '点位2电池'
                }

    # 点位3 数据线放置
    PlaceCable = { # 扫描结果（get str），比对结果（set int）, 比对触发(get int), 是否需要比对(set int)
                'scan_res': 116, 'check_res': 114, 'is_check_chufa': 113, 'need_check':[148,54], 'is_place': [148,67],'device_name': '点位3数据线'
                }

    # 点位4 螺丝 充电器 放置
    # PlaceScrew = {'is_yd': 242, 'is_jc': 240, 'set_yd_res': [243, 246, 250, 254], 'set_jc_res': 241,
    #               'get_xyu': [258, 262, 266]}
    PlaceChDQ = {  # 扫描结果（get str），比对结果（set int）, 比对触发(get int), 是否需要比对(set int)
        'scan_res': 340, 'check_res': 338, 'is_check_chufa': 337, 'need_check': [148,56], 'is_place': [148,70],'device_name': '点位4充电器'
    } # 充电器

    # 点位5 说明书放置 许可证放置
    PlaceInstruction = { # 扫描结果（get str），比对结果（set int）, 比对触发(get int), 是否需要比对(set int)
                'scan_res': 228, 'check_res': 226, 'is_check_chufa': 225, 'need_check':[148,57],  'is_place': [148, 69],'device_name': '点位5说明书'
                }
    PlaceLicence = { # 扫描结果（get str），比对结果（set int）, 比对触发(get int), 是否需要比对(set int)
                    'scan_res': 284, 'check_res': 282, 'is_check_chufa': 281, 'need_check':[148,58], 'is_place': [148,71],'device_name': '点位5许可证'
                    }  # 许可证放置

    # 点位6 腕带 DA1851911
    PlaceStrap = { # 扫描结果（get str），比对结果（set int）, 比对触发(get int), 是否需要比对(set int)
                'scan_res': 172, 'check_res': 170, 'is_check_chufa': 169, 'need_check':[148,55], 'is_place': [148,68], 'device_name': '点位6腕带'
                }

    # 地址表
    # 点位7 手机放置
    # [是否引导 uint8 复位0/不引导1/引导2 ， 是否检测 0/1 ，引导结果 [是否ok uint8, x坐标 real, y坐标 real, 角度 real,]  ， 检测结果 0/1/2]
    PlacePhone = { # 扫描结果（get str），比对结果（set int）, 比对触发(get int), 是否需要比对(set int), device name, is print done
                'scan_res': 60, 'check_res': 58, 'is_check_chufa': 57, 'need_check':[148,53], 'device_name': '点位7手机',
                }
    # PlacePhone = { # 扫描结果（get str），比对结果（set int）, 比对触发(get int), 是否需要比对(set int), device name, is print done
    #             'scan_res': 396, 'check_res': 394, 'is_check_chufa': 393, 'need_check':[148,53], 'device_name': '点位7手机',
    #
    #             }


config = Config()  #


class HuoniPlcScanClientBase: # 一个点位一个plc客户端 （各点位plc逻辑类似）

    static_lock = threading.Lock()
    def __init__(self, addressTable = Config().PlacePhone, driver= 's7_tcp'):

        # self.s7client = S7Client(config.ip, config.port, config.dbnumber)
        self.s7client = Lay1PLCFactory.create(driver, config.ip, config.port, config.dbnumber)
        self.addressTable = addressTable
        # HuoniPlcScanClientBase.static_lock = threading.Lock() # 同步
        # connect
        # self.connect()

    def connect(self):
        # HuoniPlcScanClientBase.static_lock.acquire()
        self.s7client.connect()
        # HuoniPlcScanClientBase.static_lock.release()
        print(self.addressTable)
        return self.s7client.get_connected()

    def is_connect(self):
        return self.s7client.get_connected()

    def close(self):
        self.s7client.close()

    def set_addressTable(self, addressTable = config.PlaceBat):
        self.addressTable = addressTable
        # print(self.addressTable.__name__)
        print(self.addressTable)

    def get_scan_res(self):  # 1获取扫码结果
        HuoniPlcScanClientBase.static_lock.acquire()
        scan_res_str = self.s7client.read_string(start=self.addressTable['scan_res'])
        HuoniPlcScanClientBase.static_lock.release()
        return scan_res_str

    def set_check_res(self, check_res_int):  # 2返回比对结果
        # 检测结果 0/1/2
        HuoniPlcScanClientBase.static_lock.acquire()
        start = self.addressTable['check_res']
        recode = self.s7client.write_int8(start=start, data_int8=check_res_int)
        # logger.error(f'比对结果recode {recode}')

        def get_isOK(timeout=1):
            isOK = False
            t1 = time.time()
            while time.time()-t1 < timeout: # 1s
                time.sleep(0.05)
                value = self.s7client.read_int8(start=start)
                # if not value == 0:
                if value == check_res_int:
                    isOK = True
                    logger.info(f'比对结果xx isOK {isOK}')
                    break
                else:
                    logger.info(f'比对结果 write{check_res_int} read {value}')
            return isOK

        if not get_isOK(timeout=1):
            logger.info(f'比对结果 write twice')
            self.s7client.write_int8(start=self.addressTable['check_res'], data_int8=check_res_int)
            if not get_isOK(timeout=1):
                logger.info(f'比对结果 write third')
                self.s7client.write_int8(start=self.addressTable['check_res'], data_int8=check_res_int)
                if not get_isOK(timeout=1):
                    logger.error(f'比对结果 write false third')
        HuoniPlcScanClientBase.static_lock.release()


    def get_is_scan_done(self):  # 3获取比对触发
        is_yd = 0
        HuoniPlcScanClientBase.static_lock.acquire()
        try:
            # HuoniPlcScanClientBase.static_lock.acquire()
            # db, start = self.addressTable['is_check_chufa']
            is_yd = self.s7client.read_int8(start=self.addressTable['is_check_chufa'])
            # HuoniPlcScanClientBase.static_lock.release()
        except Exception as e:
            # HuoniPlcScanClientBase.static_lock.release() # 20240605
            # logger.error(e)
            logger.error(traceback.format_exc())
            # HuoniPlcScanClientBase.static_lock.acquire()
            try:
                self.close()
                logger.info('re connecting')
                self.connect() # 重连
            except Exception as e:
                logger.error(traceback.format_exc())
            # HuoniPlcScanClientBase.static_lock.release()
            # return 0
        HuoniPlcScanClientBase.static_lock.release()
        return is_yd
    def set_zero_is_scan_done(self):  #
        HuoniPlcScanClientBase.static_lock.acquire()
        # dbnumber,start = self.addressTable['is_check_chufa']
        self.s7client.write_int8(start=self.addressTable['is_check_chufa'], data_int8=0)
        HuoniPlcScanClientBase.static_lock.release()


    def set_is_need_check(self, need_check_int):  # 4 输出是否需要比对
        # 检测结果 0/1/2
        HuoniPlcScanClientBase.static_lock.acquire()
        dbnumber,start = self.addressTable['need_check']
        self.s7client.write_int8(start=start, data_int8=need_check_int, dbnumber=dbnumber)
        HuoniPlcScanClientBase.static_lock.release()

    def set_scan_res(self, data_scan = 'hello'):  # set 扫码结果
        HuoniPlcScanClientBase.static_lock.acquire()
        scan_res_str = self.s7client.write_string(start=self.addressTable['scan_res'], data_str=data_scan)
        HuoniPlcScanClientBase.static_lock.release()
        return scan_res_str

    def wait_plc(self, get_chufa_fun1, set_zeros_fun2,
                 is_continue_fun_1=None):  # self.get_is_scan_done, set_zero_is_scan_done
        '''
        usage:
        self.wait(self.get_is_scan_done, self.set_zero_is_scan_done)
        :param get_chufa_fun1:
        :param set_zeros_fun2:
        :return:
        '''
        isDone = 0  # 1 ture 2 false
        while isDone == 0:
            isDone = get_chufa_fun1()  #
            # print(isDone)
            if is_continue_fun_1 is not None and not is_continue_fun_1():  # break when is_continue_fun_1 is false
                break

            time.sleep(0.1)  # s
        set_zeros_fun2()  # to 0
        # print(f"{self.addressTable['device_name']} isDone {isDone}")

    '''
    for print
    '''
    def set_is_print_done(self, is_done_int = 0):  # set
        HuoniPlcScanClientBase.static_lock.acquire()
        dbnumber, start = self.addressTable['is_print_done']
        scan_res_str = self.s7client.write_int8(start=start, data_int8=is_done_int, dbnumber=dbnumber)
        HuoniPlcScanClientBase.static_lock.release()
        return scan_res_str

    def get_is_print_chufa(self):
        is_yd = 0
        HuoniPlcScanClientBase.static_lock.acquire()
        try:
            # HuoniPlcScanClientBase.static_lock.acquire()
            dbnumber, start = self.addressTable['is_print_chufa']
            is_yd = self.s7client.read_int8(start=start, dbnumber=dbnumber)
            # HuoniPlcScanClientBase.static_lock.release()
        except Exception as e:
            # logger.error(e)
            logger.error(traceback.format_exc())
            # HuoniPlcScanClientBase.static_lock.acquire()
            try:
                self.close()
                logger.info('re connecting')
                self.connect() # 重连
            except Exception as e:
                logger.error(traceback.format_exc())
            # HuoniPlcScanClientBase.static_lock.release()
            # return 0
        HuoniPlcScanClientBase.static_lock.release()
        return is_yd

    def set_zero_is_print_chufa(self):  #
        HuoniPlcScanClientBase.static_lock.acquire()
        dbnumber, start = self.addressTable['is_print_chufa']
        self.s7client.write_int8(start=start, data_int8=0, dbnumber=dbnumber)
        HuoniPlcScanClientBase.static_lock.release()

    '''
    for upload server
    '''

    def get_is_upload_chufa(self):
        is_yd = 0
        HuoniPlcScanClientBase.static_lock.acquire()
        try:
            # HuoniPlcScanClientBase.static_lock.acquire()
            dbnumber, start = self.addressTable['is_upload_chufa']
            is_yd = self.s7client.read_int8(start=start, dbnumber=dbnumber)
            # HuoniPlcScanClientBase.static_lock.release()
        except Exception as e:
            # logger.error(e)
            logger.error(traceback.format_exc())
            # HuoniPlcScanClientBase.static_lock.acquire()
            try:
                self.close()
                logger.info('re connecting')
                self.connect() # 重连
            except Exception as e:
                logger.error(traceback.format_exc())
            # HuoniPlcScanClientBase.static_lock.release()
            # return 0
        HuoniPlcScanClientBase.static_lock.release()
        return is_yd

    def set_zero_is_upload_chufa(self):  #
        HuoniPlcScanClientBase.static_lock.acquire()
        dbnumber, start = self.addressTable['is_upload_chufa']
        self.s7client.write_int8(start=start, data_int8=0, dbnumber=dbnumber)
        HuoniPlcScanClientBase.static_lock.release()

    def set_is_upload_done(self, is_done_int = 0):  # set
        # HuoniPlcScanClientBase.static_lock.acquire()
        # dbnumber, start = self.addressTable['is_upload_done']
        # self.s7client.write_int8(start=start, data_int8=is_done_int, dbnumber=dbnumber)
        # HuoniPlcScanClientBase.static_lock.release()

        dbnumber, start = self.addressTable['is_upload_done']
        self.safe_write_int8(start=start, data_int8=is_done_int, dbnumber=dbnumber)


    def get_serial_number(self): # get serial number to print

        HuoniPlcScanClientBase.static_lock.acquire()
        dbnumber, start = self.addressTable['serial_number_to_print']
        scan_res_str = self.s7client.read_string(start=start, dbnumber=dbnumber)
        HuoniPlcScanClientBase.static_lock.release()
        return scan_res_str

    def get_serial_number_to_upload(self): # 获取上传序列号
        HuoniPlcScanClientBase.static_lock.acquire()
        dbnumber, start = self.addressTable['serial_number_to_upload']
        scan_res_str = self.s7client.read_string(start=start, dbnumber=dbnumber)
        HuoniPlcScanClientBase.static_lock.release()
        return scan_res_str
    # next layer


    '''
    below is for MainTh
    '''
    def set_is_change_product_chufa(self):
        HuoniPlcScanClientBase.static_lock.acquire()
        dbnumber, start = self.addressTable['is_change_product_chufa']
        self.s7client.write_int8(start=start, data_int8=1, dbnumber=dbnumber)
        HuoniPlcScanClientBase.static_lock.release()
    def set_sku(self, data_scan = 'hello'):  # set product sku
        HuoniPlcScanClientBase.static_lock.acquire()
        dbnumber, start = self.addressTable['sku']
        scan_res_str = self.s7client.write_string(start=start, data_str=data_scan, dbnumber=dbnumber)
        HuoniPlcScanClientBase.static_lock.release()
        return scan_res_str

    def set_sku_int(self, sku_int = 0):  # set product sku
        HuoniPlcScanClientBase.static_lock.acquire()
        dbnumber, start = self.addressTable['sku_int']
        scan_res_str = self.s7client.write_int8(start=start, data_int8=sku_int, dbnumber=dbnumber)
        HuoniPlcScanClientBase.static_lock.release()
        return scan_res_str

    def set_is_ready(self):
        HuoniPlcScanClientBase.static_lock.acquire()
        dbnumber, address = self.addressTable['is_ready']
        self.s7client.write_int8(start=address, data_int8=1, dbnumber=dbnumber)
        HuoniPlcScanClientBase.static_lock.release()
    def set_not_ready(self):
        HuoniPlcScanClientBase.static_lock.acquire()
        dbnumber, address = self.addressTable['is_ready']
        self.s7client.write_int8(start=address, data_int8=0, dbnumber=dbnumber)
        HuoniPlcScanClientBase.static_lock.release()
        # return scan_res_str

    def set_place_infor(self, running_place_pns, pn_plc_dict):
        pass
        try:
            # 置零
            plcs = pn_plc_dict.values()
            for plc in plcs:
                plc_config_address = eval(plc[4:])
                dbnumber, address = plc_config_address['is_place']
                self.safe_write_int8(start=address, data_int8=0, dbnumber=dbnumber)
                # break
            time.sleep(0.5)

            for pn_str in running_place_pns:
                # if not pn_plc_dict.get(pn_str):
                #     continue
                plc_config_address = eval(pn_plc_dict[pn_str][4:]) # plc_config.PlaceStrap
                dbnumber, address = plc_config_address['is_place']
                self.safe_write_int8(start=address, data_int8=1, dbnumber=dbnumber)

            dbnumber, address = config.MainTh['place_num']
            self.safe_write_int8(start=address, data_int8=len(running_place_pns), dbnumber=dbnumber)
        except:
            logger.error('向PLC发送 物料是否放置信息 异常')
            logger.info(traceback.format_exc())


    def safe_write_int8(self, start=0, data_int8=0, dbnumber=0):

        HuoniPlcScanClientBase.static_lock.acquire()
        self.s7client.write_int8(start=start, data_int8=data_int8, dbnumber=dbnumber)
        # self.s7client.write_int8(start=start, data_int8=data_int8, dbnumber=dbnumber)
        # print(f'startxxx {start}')
        def get_isOK(timeout=1):
            isOK = False
            t1 = time.time()
            while time.time()-t1 < timeout: # 1s
                time.sleep(0.05)
                value = self.s7client.read_int8(start=start, dbnumber=dbnumber)
                print(f'start {start}')
                if value == data_int8:
                    isOK = True
                    # logger.info(f'write {data_int8} read_int8 {value}')
                    break
                else:
                    logger.info(f'write {data_int8} read_int8 {value}')
            return isOK

        if not get_isOK(timeout=0.25):
            logger.info(f'write_int8  twice')
            self.s7client.write_int8(start=start, data_int8=data_int8, dbnumber=dbnumber)

        HuoniPlcScanClientBase.static_lock.release()

    '''
    用于手动触发
    '''
    def munal_set_chufa(self, adds_list_or_int = 0):
        '''
        手动设置触发, 适配plc_config
        usage:
        self.munal_set_chufa(plc_config.MainTh['is_print_chufa'])

        :param adds_list_or_int:
        :return:
        '''
        HuoniPlcScanClientBase.static_lock.acquire()
        if isinstance(adds_list_or_int, list):
            dbnumber, address = adds_list_or_int
            self.s7client.write_int8(start=address, data_int8=1, dbnumber=dbnumber)
        else:
            self.s7client.write_int8(start=adds_list_or_int, data_int8=1)
        HuoniPlcScanClientBase.static_lock.release()

    def munal_get_chufa(self, adds_list_or_int=0):
        '''
        手动获取触发值, 适配plc_config
        usage:
        value = self.munal_get_chufa(plc_config.MainTh['is_print_chufa'])

        :param adds_list_or_int:
        :return:
        '''
        HuoniPlcScanClientBase.static_lock.acquire()
        if isinstance(adds_list_or_int, list):
            dbnumber, address = adds_list_or_int
            value = self.s7client.read_int8(start=address, dbnumber=dbnumber)
        else:
            value = self.s7client.read_int8(start=adds_list_or_int)
        HuoniPlcScanClientBase.static_lock.release()
        return value

    def munal_set_str(self, adds_list_or_int = 0, value_str=''):
        '''
        手动设置, 适配plc_config
        :param adds_list_or_int:
        :return:
        '''
        HuoniPlcScanClientBase.static_lock.acquire()
        if isinstance(adds_list_or_int, list):
            dbnumber, address = adds_list_or_int
            self.s7client.write_string(start=address, data_str=value_str, dbnumber=dbnumber)
        else:
            self.s7client.write_string(start=adds_list_or_int, data_str=value_str)
        HuoniPlcScanClientBase.static_lock.release()

class SimHuoniPlcScanClientBase(HuoniPlcScanClientBase): # 一个点位一个plc客户端 （各点位plc逻辑类似）

    def __init__(self, addressTable = Config().PlacePhone):
        self.addressTable = addressTable
        self.data_ = []
        pass

    def get_is_upload_chufa(self):
        return random.random()>0.9

    def set_zero_is_upload_chufa(self):
        pass

    def set_is_upload_done(self, is_done_int=0):
        pass

    def set_sku_int(self, sku_int=0):
        pass


    def connect(self):
        pass

    def close(self):
        pass
    def set_addressTable(self, addressTable = config.PlaceBat):
        pass
    def get_scan_res(self):  # 1获取扫码结果
        return ' '
    def set_check_res(self, check_res_int):  # 2返回比对结果
        # 检测结果 0/1/2
        pass
    def get_is_scan_done(self):  # 3获取比对触发
        return 0
    def set_zero_is_scan_done(self):  #
        pass

    def set_is_need_check(self, need_check_int):  # 4 输出是否需要比对
        # 检测结果 0/1/2
        pass

    def set_scan_res(self, data_scan = 'hello'):  # set 扫码结果
        pass

    def set_is_print_done(self, is_done_int = 0):  # set
        pass

    def get_is_print_chufa(self):
        return 0

    def set_zero_is_print_chufa(self):  #
        pass

    def get_serial_number(self): # get serial number to print
        return ' '

    # next layer
    def wait_plc(self, get_chufa_fun1, set_zeros_fun2,is_continue_fun_1=None): # self.get_is_scan_done, set_zero_is_scan_done
        '''
        usage:
        self.wait(self.get_is_scan_done, self.set_zero_is_scan_done)
        :param get_chufa_fun1:
        :param set_zeros_fun2:
        :return:
        '''
        time.sleep(1)  # s

    def set_is_ready(self):
        pass
    def set_not_ready(self):
        pass

    def set_sku(self, data_scan='hello'):
        pass
    def set_is_change_product_chufa(self):
        pass

def test_HuoniPlcClinet():
    hn_plc = HuoniPlcScanClientBase(addressTable=config.PlaceBat)
    hn_plc.connect()

    # 1 judge is check by product from mess
    print(f'1 judge is check by product from mess')
    isCheck = 1 # get isCheck from mess, 1 ture 2 false todo
    hn_plc.set_is_need_check(isCheck) #
    print(f'isCheck {isCheck}')
    if not isCheck:
        return

    # 2 wait scan done
    print(f'2 wait scan done')
    isDone = 2 # 1 ture 2 false
    while isDone == 2:
        isDone = hn_plc.get_is_scan_done()
        time.sleep(0.1) # s
    print(f'isDone {isDone}')

    # 3 get scan res and check by mess
    print(f'3 get scan res and check by mess')
    scan_res = hn_plc.get_scan_res()
    mess_res = '12345678' # get code from mess todo
    print(f'scan_res {scan_res}')
    print(f'mess_res {mess_res}')
    isRight = 2 # 1 ture 2 false
    if scan_res == mess_res:
        isRight = 1

    # 4 return isRight to plc
    print(f'4 return isRight to plc')
    hn_plc.set_check_res(isRight)
    print(f'isRight {isRight}')

    hn_plc.set_scan_res('!123qwer12fsfder3')
    # value = hn_plc.get_scan_res()
    # print(value)

    hn_plc.close()




if __name__ == "__main__":
    test_HuoniPlcClinet()

    # HuoniPlcScanClientBase().work_thread()

    # HuoniPlcScanClientBase(Config().MainTh).set_sku('C')  # set 1
